/*
 *  ETA/OS - Python VM
 *  Copyright (C) 2017   Michel Megens <dev@bietje.net>
 *  Copyright (C) 2017   Dean Hall
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#undef __FILE_ID__
#define __FILE_ID__ 0x01

/**
 * \file
 * \brief CodeObj Type
 *
 * CodeObj type operations.
 */

#include <etaos/kernel.h>
#include <etaos/python.h>

/* The image format is defined by co_to_str() in src/tools/pmImgCreator.py */
PmReturn_t
co_loadFromImg(PmMemSpace_t memspace, uint8_t const **paddr, pPmObj_t * r_pco)
{
	PmReturn_t retval = PM_RET_OK;
	pPmObj_t pobj;
	pPmCo_t pco = C_NULL;
	uint8_t *pchunk;
	uint8_t objid;
#ifdef HAVE_DEBUG_INFO
	uint8_t objtype;
	uint16_t len_str;
#endif				/* HAVE_DEBUG_INFO */

	/* Store ptr to top of code img (less type byte) */
	uint8_t const *pci = *paddr - 1;

	/* Get size of code img */
	uint16_t size = mem_getWord(memspace, paddr);

#ifdef HAVE_DEBUG_INFO
	objtype = 0;
	USED(objtype);
#endif

	/* Allocate a code obj */
	retval = heap_getChunk(sizeof(PmCo_t), &pchunk);
	PM_RETURN_IF_ERROR(retval);
	pco = (pPmCo_t) pchunk;

	/* Fill in the CO struct */
	OBJ_SET_TYPE(pco, OBJ_TYPE_COB);
	pco->co_memspace = memspace;
	pco->co_argcount = mem_getByte(memspace, paddr);
	pco->co_flags = mem_getByte(memspace, paddr);
	pco->co_stacksize = mem_getByte(memspace, paddr);
	pco->co_nlocals = mem_getByte(memspace, paddr);

	/* Do not set code image address if image is in RAM.
	 * CIs in RAM have their image address set in obj_loadFromImgObj() */
	pco->co_codeimgaddr = (memspace == MEMSPACE_RAM) ? C_NULL : pci;

	/* Set these to null in case a GC occurs before their objects are alloc'd */
	pco->co_names = C_NULL;
	pco->co_consts = C_NULL;
	pco->co_codeaddr = C_NULL;

#ifdef HAVE_CLOSURES
	pco->co_nfreevars = mem_getByte(memspace, paddr);
	pco->co_cellvars = C_NULL;
#endif				/* HAVE_CLOSURES */

#ifdef HAVE_DEBUG_INFO
	pco->co_firstlineno = mem_getWord(memspace, paddr);
	pco->co_lnotab = C_NULL;
	pco->co_filename = C_NULL;
#endif				/* HAVE_DEBUG_INFO */

	/* Load names (tuple obj) */
	heap_gcPushTempRoot((pPmObj_t) pco, &objid);
	retval = obj_loadFromImg(memspace, paddr, &pobj);
	heap_gcPopTempRoot(objid);
	PM_RETURN_IF_ERROR(retval);
	pco->co_names = (pPmTuple_t) pobj;

#ifdef HAVE_DEBUG_INFO
	/* Get address in memspace of line number table (including length) */
	objtype = mem_getByte(memspace, paddr);
	C_ASSERT(objtype == OBJ_TYPE_STR);
	pco->co_lnotab = *paddr;
	len_str = mem_getWord(memspace, paddr);
	*paddr = *paddr + len_str;

	/* Get address in memspace of CO's filename (excluding length) */
	objtype = mem_getByte(memspace, paddr);
	C_ASSERT(objtype == OBJ_TYPE_STR);
	len_str = mem_getWord(memspace, paddr);
	pco->co_filename = *paddr;
	*paddr = *paddr + len_str;
#endif				/* HAVE_DEBUG_INFO */

	/* Load consts (tuple obj) assume it follows names */
	heap_gcPushTempRoot((pPmObj_t) pco, &objid);
	retval = obj_loadFromImg(memspace, paddr, &pobj);
	heap_gcPopTempRoot(objid);
	PM_RETURN_IF_ERROR(retval);
	pco->co_consts = (pPmTuple_t) pobj;

#ifdef HAVE_CLOSURES
	heap_gcPushTempRoot((pPmObj_t) pco, &objid);
	retval = obj_loadFromImg(memspace, paddr, &pobj);
	heap_gcPopTempRoot(objid);
	PM_RETURN_IF_ERROR(retval);

	/* Save RAM, don't keep empty tuple */
	if (((pPmTuple_t) pobj)->length == 0) {
		heap_freeChunk(pobj);
	} else {
		pco->co_cellvars = (pPmTuple_t) pobj;
	}
#endif				/* HAVE_CLOSURES */

	/* Start of bcode always follows consts */
	pco->co_codeaddr = *paddr;

	/* Set addr to point one past end of img */
	*paddr = pci + size;

	*r_pco = (pPmObj_t) pco;
	return PM_RET_OK;
}

void co_rSetCodeImgAddr(pPmCo_t pco, uint8_t const *pimg)
{
	uint8_t i;

	pco->co_codeimgaddr = pimg;

	/* Set the image address for any COs in the constant pool */
	for (i = 0; i < pco->co_consts->length; i++) {
		if (OBJ_GET_TYPE(pco->co_consts->val[i]) == OBJ_TYPE_COB) {
			co_rSetCodeImgAddr((pPmCo_t) pco->co_consts->val[i],
					   pimg);
		}
	}

	return;
}

PmReturn_t
no_loadFromImg(PmMemSpace_t memspace, uint8_t const **paddr, pPmObj_t * r_pno)
{
	PmReturn_t retval = PM_RET_OK;
	pPmNo_t pno = C_NULL;
	uint8_t *pchunk;

	/* Allocate a code obj */
	retval = heap_getChunk(sizeof(PmNo_t), &pchunk);
	PM_RETURN_IF_ERROR(retval);
	pno = (pPmNo_t) pchunk;

	/* Fill in the NO struct */
	OBJ_SET_TYPE(pno, OBJ_TYPE_NOB);
	pno->no_argcount = mem_getByte(memspace, paddr);

	/* Get index into native fxn table */
	pno->no_funcindx = (int16_t) mem_getWord(memspace, paddr);

	*r_pno = (pPmObj_t) pno;
	return PM_RET_OK;
}
